home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1999 June
/
Macworld (1999-06).dmg
/
Shareware World
/
Info
/
For Developers
/
MacZoop2.0.sea
/
MacZoop2.0
/
Required Classes
/
ZApplication.cpp
< prev
next >
Wrap
Text File
|
1999-02-24
|
50KB
|
1,866 lines
/*************************************************************************************************
*
*
* MacZoop - "the framework for the rest of us"
*
*
*
* ZApplication.cpp -- the application object
*
*
*
*
*
* © 1996, Graham Cox
*
*
*
*
*************************************************************************************************/
#include "MacZoop.h"
#include "ProjectSettings.h"
#include "ZEventHandler.h"
#include "ZWindow.h"
#include "ZPrinter.h"
#include "ZUndoTask.h"
#ifdef USER_DEFAULT_WINDOW_TYPE
#include HEADER( USER_DEFAULT_WINDOW_TYPE )
#endif
#if _USE_NAVIGATION_SERVICES
#include <Navigation.h>
#endif
#include <StandardFile.h>
#include <Notification.h>
#include <dialogs.h>
// gApplication is the global application object. There is only one, naturally.
ZApplication* gApplication = NULL;
// globals inited by this class
ZMenuBar* gMenuBar = NULL; // the main menubar object
OSType gAppSignature; // the application's signature, obtained from 'BNDL', etc.
tMacInfo gMacInfo; // common gestalt results
RgnHandle gUtilRgn = NULL; // general purpose region, handy as temp variable. Do NOT dispose!
ZPrefsFile* gPrefsFile = NULL; // NOT inited unless your application subclass does it!
OSErr gDragErr = noErr; // Set if Drag/Drop produced an error
// a set of useful colours, available as global RGBColor records
RGBColor gWhite = { 0xFFFF, 0xFFFF, 0xFFFF }; // white RGB colour
RGBColor gBlack = { 0x0000, 0x0000, 0x0000 }; // black RGB colour
RGBColor gLightGray = { 0xDDDD, 0xDDDD, 0xDDDD }; // light gray RGB colour
RGBColor gMidGray = { 0x7F7F, 0x7F7F, 0x7F7F }; // medium gray RGB colour
RGBColor gDarkGray = { 0x5555, 0x5555, 0x5555 }; // dark gray RGB colour
RGBColor gRed = { 0xFFFF, 0x0000, 0x0000 }; // solid red RGB colour
RGBColor gGreen = { 0x0000, 0xFFFF, 0x0000 }; // solid green RGB colour
RGBColor gBlue = { 0x0000, 0x0000, 0xFFFF }; // solid blue RGB colour
RGBColor gCyan = { 0x0000, 0xFFFF, 0xFFFF }; // solid cyan RGB colour
RGBColor gMagenta = { 0xFFFF, 0x0000, 0xFFFF }; // solid magenta RGB colour
RGBColor gYellow = { 0xFFFF, 0xFFFF, 0x0000 }; // solid yellow RGB colour
// static functions used by the application object
static pascal long ZGrowFunc( Size bytesShort );
static GrowZoneUPP gGZFunc = NewGrowZoneProc( ZGrowFunc );
#if _USE_NAVIGATION_SERVICES
static pascal void ZNavEventCallback( NavEventCallbackMessage cbMessage,
NavCBRecPtr cbParams,
NavCallBackUserData cbData );
NavEventUPP gNavEventHandler = NewNavEventProc( ZNavEventCallback );
#endif
extern void TimerTimer();
// note: ZApplication is NEVER instantiated from a stream- to ensure this but keep
// special cases out of the class registry, etc, we jig the construction function
// to simply return gApplication.
ZObject* CF_ZApplication()
{
return gApplication;
}
extern ZObject* CF_ZObjectList();
/*--------------------------------*** CONSTRUCTOR ***---------------------------------*/
ZApplication::ZApplication()
: ZCommander( NULL )
{
long features;
OSErr theErr;
classID = CLASS_ZApplication;
gUtilRgn = NewRgn();
done = FALSE;
phase = kInitialising;
zEH = NULL;
curUndoTask = NULL;
itsPrinter = NULL;
splashVisible = FALSE;
msDepth = 0;
appResRefNum = CurResFile();
// set initial filetypes list to a zero handle
itsFileTypes = (FTypeListHdl) NewHandleClear( sizeof( FTypeList ) - sizeof( OSType ));
shortageFund = NULL;
memIsShort = FALSE;
userHasSeenAlert = FALSE;
// check to see if we are running on a colour Mac
theErr = Gestalt( gestaltQuickdrawFeatures, &features );
gMacInfo.supportsColour = ((theErr == noErr) && (features & 1));
// check for the drag manager
theErr = Gestalt( gestaltDragMgrAttr, &features );
gMacInfo.hasDragManager = ((theErr == noErr) && (features & 1));
// check for a FPU
theErr = Gestalt( gestaltFPUType, &features );
gMacInfo.hasFPU = ((theErr == noErr) && ((features & 1) == 0));
// check for applescript
theErr = Gestalt( gestaltAppleEventsAttr, &features );
gMacInfo.hasAppleEvents = ((theErr == noErr) && (features & 1));
// check for QuickTime™
theErr = Gestalt( gestaltQuickTimeVersion, &features );
gMacInfo.hasQuickTime = ((theErr == noErr) && (features > 0));
// check for ICM
theErr = Gestalt( gestaltCompressionMgr, &features );
gMacInfo.hasImgCompressionMgr = (theErr == noErr);
// check for appearance manager
#if APPEARANCE_MGR_AWARE
theErr = Gestalt( gestaltAppearanceAttr, &features );
gMacInfo.hasAppearanceMgr = (( theErr == noErr ) && ( features & 1 ));
#if __powerc
if ( gMacInfo.hasAppearanceMgr )
gMacInfo.hasAppearanceMgr = ((long) CreateRootControl != kUnresolvedCFragSymbolAddress );
#endif
#else
gMacInfo.hasAppearanceMgr = FALSE;
#endif
// what is the system version?
theErr = Gestalt( gestaltSystemVersion, &features );
gMacInfo.systemVersion = LoWord( features );
// check for navigation services:
#if _USE_NAVIGATION_SERVICES
if ( gMacInfo.systemVersion >= 0x0755 )
gMacInfo.hasNavigationServices = NavServicesAvailable();
#else
gMacInfo.hasNavigationServices = FALSE;
#endif
// check for contextual menus:
if ( gMacInfo.systemVersion >= 0x0800 )
gMacInfo.hasContextualMenus = TRUE;
else
gMacInfo.hasContextualMenus = FALSE;
// initialise the animating cursors, and set the
// animated watch cursor going.
AppCursorInit();
gApplication = this;
}
/*--------------------------------*** DESTRUCTOR ***---------------------------------*/
ZApplication::~ZApplication()
{
#if __ZPREFSFILE__
if ( gPrefsFile )
ForgetObject( gPrefsFile );
#endif
if ( gMenuBar )
ForgetObject( gMenuBar );
#if _SLOW_BUT_SURE_DESTRUCTION
// this stuff is destructed only if the above is set to ON, but note that
// if OFF, the app will quit normally, and quickly, relying on the heap
// being freed in one go when the app exits to shell. This is NOT a memory
// leak!!! Since all objects are allocated in the heap, the memory is freed
// when the heap is freed. However, the destructors are not called in this case.
if ( gClipboard )
ForgetObject( gClipboard );
if ( gWindowManager )
ForgetObject( gWindowManager );
if ( zEH )
ForgetObject( zEH );
if ( itsPrinter )
ForgetObject( itsPrinter );
if ( curUndoTask )
ForgetObject( curUndoTask );
if ( itsFileTypes )
DisposeHandle((Handle) itsFileTypes);
if ( shortageFund )
DisposeHandle( shortageFund );
DisposeRgn( gUtilRgn );
AppCursorFree();
#endif
StopCursorAnimation();
}
/*--------------------------------*** INITMACZOOP ***---------------------------------*/
/*
Initialises the application, makes the event handler object and the menubar
----------------------------------------------------------------------------------------*/
void ZApplication::InitMacZoop( const short numMasterBlocks )
{
if ( phase == kInitialising )
{
// init the mac toolbox, memory mangler etc.
RegisterClasses();
InitMacApplication( numMasterBlocks );
// create the memory shortage fund
FailNIL( shortageFund = NewHandle( kShortageFundSize ));
SetGrowZone( gGZFunc );
SetWatchCursor();
// make the helper objects
MakeHelpers();
// make the clipboard object
MakeClipboard();
// read any prefs that the user may have set up (default does nothing). A typical
// thing to do here is to simply instantiate a ZPrefsFile object and assign it to
// <gPrefsFile>. You may also want to open/read the file! (use of data or resource
// fork is up to you, so ZPrefsFile does not automatically perform the open step).
ReadPrefs();
ShowSplash();
// initialise menubar and make printer object
InitMenuBar();
MakePrinter();
// preload the navigation services if required, to make use of these faster later on.
#if _USE_NAVIGATION_SERVICES
if ( gMacInfo.hasNavigationServices )
FailOSErr( NavLoad());
#endif
// call user-function as last part of initialisation- default
// does nothing but can be overridden to do further set up.
StartUp();
// record depth of main screen
msDepth = GetMainScreenDepth();
// update the menubar- note that this is not drawn until StartUp completes,
// since we allow the programmer the flexibility to use StartUp to create & install
// dynamic menus, etc. if they want.
gMenuBar->UpdateMenuBar();
// the cursor is not reset here- we let the main event loop do that so that if
// events are initially available, the cursor keeps right on animating until
// the user gets a chance to do anything.
}
}
/*-----------------------------*** INITMACAPPLICATION ***-----------------------------*/
/*
Initialises the mac toolbox and memory manager
----------------------------------------------------------------------------------------*/
void ZApplication::InitMacApplication( const short numMasterBlocks )
{
// chant the "Macintosh mantra"...
InitGraf( &qd.thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( NULL );
// clear out the event queue in case any stray clicks or keypresses are left there
FlushEvents( everyEvent, 0 );
// see if the program can run on this Mac- if not, we show an alert and exit straight away
if (! CheckCanRun())
{
StopCursorAnimation();
(void) Alert( kCantRunAlertID, NULL );
ExitToShell();
}
else
{
// give yourself enough memory. For a bigger app, you may need to call MoreMasters a few
// more times. The parameter to this method sets the number of times MoreMasters is
// called, defaulting to 8, which gives 8 x 64 = 512 handles.
MaxApplZone();
short n = numMasterBlocks;
while( n-- )
MoreMasters();
// if we want appearance, register the app:
#if APPEARANCE_MGR_AWARE
if ( gMacInfo.hasAppearanceMgr )
FailOSErr( RegisterAppearanceClient());
#endif
// set up <gAppSignature>, either from BNDL resource, or from constant according
// to the user's project settings.
#if USE_SIGNATURE_FROM_BNDL
OSType** bndlHand = (OSType**) GetResource( 'BNDL', 128 );
if ( bndlHand )
{
gAppSignature = **bndlHand;
ReleaseResource((Handle) bndlHand );
#if CHECK_FREF_RESOURCE_TYPES
// first look for an 'open' resource ID=128. If there is one, we use it in
// preference to the FREF's, since there is a slightly different meaning. An 'open'
// resource can be treated as a list of OSTypes if you ignore the first 8 bytes.
bndlHand = (OSType**) GetResource( 'open', 128 );
if ( bndlHand )
{
// we have an 'open' resource, so just copy the data (ignoring first 8 bytes)
// to the file types list. Warning: This technique does not check for duplicates,
// either in the resource or in the types list.
long bc = GetHandleSize((Handle) bndlHand );
SetHandleSize((Handle) itsFileTypes, bc );
FailMemError();
BlockMoveData((Ptr) *bndlHand, (Ptr) *itsFileTypes, bc );
ReleaseResource((Handle) bndlHand );
}
else
{
// scan the 'FREF' resources and build the <itsFileTypes> list. We do NOT add
// types of 'APPL', 'cdev', 'INIT', or 'rdev'- if you want to display such
// files in the Open dialog, you have to call AddFileType() for these types directly.
// This is a feature to prevent weird behaviour in the typical case.
// how many 'FREF's do we have?
short i, fc = Count1Resources( 'FREF' );
for( i = 1; i <= fc; i++ )
{
bndlHand = (OSType**) GetIndResource( 'FREF', i );
if ( bndlHand )
{
// check if this is one of our 'forbidden' types:
if ( **bndlHand != 'APPL' &&
**bndlHand != 'APPC' &&
**bndlHand != 'APPD' &&
**bndlHand != 'cdev' &&
**bndlHand != 'rdev' &&
**bndlHand != 'INIT' )
AddFileType( **bndlHand );
ReleaseResource((Handle) bndlHand );
}
}
}
#endif
}
else
gAppSignature = kUnknownSignature;
#else
gAppSignature = kApplicationSignature;
#endif
(*itsFileTypes)->appSignature = gAppSignature;
}
}
/*--------------------------------*** CHECKCANRUN ***---------------------------------*/
/*
Examine the machine environment to see if this will run on this Mac. By default, it can
on System 7 or later.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::CheckCanRun()
{
// Returning FALSE will cause the program to
// immediately show an alert and quit.
// by default, we can run on any Mac with System 7.0 or later.
return( gMacInfo.systemVersion >= 0x0700 );
}
#pragma mark -
/*----------------------------------*** GETNAME ***-----------------------------------*/
/*
Get the user-visible name of the application, as seen in the Finder. This works even if
the user has renamed the app, since it finds the filename.
----------------------------------------------------------------------------------------*/
void ZApplication::GetName( Str255 appName )
{
CopyPString( LMGetCurApName(), appName );
}
/*------------------------------------*** RUN ***-------------------------------------*/
/*
Fetch events and handle them until the user quits. The very first time this is entered,
phase is set to kInitialising, so we first call RunFirstTask(), which is your ideal
opportunity to create the fixed user interface, remove splash screens, etc. This is a
better place than StartUp(), since you are protected by the standard error handling.
An exception from StartUp() will fatally abort app launch, here you will continue.
----------------------------------------------------------------------------------------*/
void ZApplication::Run()
{
// runs the show by asking the event object to get events and handle them. This goes on
// forever until done is set to TRUE.
while(! done)
{
try
{
if ( phase == kInitialising )
{
phase = kRunning;
RunFirstTask();
}
// repeatedly get an event, handle an event
Process1Event();
// deal with the memory shortage situation, if one has arisen as
// a result of the last event.
CheckLowMemory();
// check for a drag error resulting from the last drag (if any)
if ( gDragErr )
{
HandleError( gDragErr );
gDragErr = noErr;
}
}
catch( OSErr theErr )
{
// if here, an exception was thrown. Deal with the error by calling the
// application's HandleError() method.
StopCursorAnimation();
HandleError( theErr );
// the buck stops here- no exceptions will be thrown beyond this point.
}
catch(...)
{
// catch anything that was thrown that is not an OSErr. This may happen if the runtime
// lib threw an exception for operator new, etc. At least in this case we can try to keep
// running.
StopCursorAnimation();
HandleError( kRuntimeLibException );
}
// stop any animating cursors. This means that a lengthy process need
// only set the cursor going and can then forget about it. When the
// app resumes handling events, it will be automatically cancelled.
StopCursorAnimation();
}
done = TRUE;
phase = kQuitting;
}
/*-----------------------------*** CHECKLOWMEMORY ***---------------------------------*/
/*
check if a low memory situation has arisen. If so, inform the user and manage the re-
plenishment of the shortage fund.
----------------------------------------------------------------------------------------*/
void ZApplication::CheckLowMemory()
{
if ( memIsShort )
{
// some of the shortage fund was used. Try to replenish it:
memIsShort = FALSE;
Handle temp = NewHandle( kShortageFundSize );
// if that resets memIsShort, then the grow zone func was called, so we
// are not in the clear yet. However, if the grow zone func wasn't
// called, then we can safely get rid of that handle and replace it with
// this one
if ( memIsShort )
{
if ( temp )
DisposeHandle( temp );
// couldn't replenish the fund, so if the user hasn't seen the
// warning yet, show it now.
if (! userHasSeenAlert)
{
StopCursorAnimation();
(void) NotifyAlert( kMemoryLowAlertID );
userHasSeenAlert = TRUE;
}
// if memIsShort is TRUE, it affects UpdateMenus such that New and Open are
// greyed out. This is an attempt to stop the user creating things that will
// eat up even more memory. You might want to use the same technique for commands
// of your own that may allocate lots of memory. For this reason, memIsShort is
// a public member.
}
else
{
// fund replenished, so get rid of any remaining fund and replace it
// with the newly allocated shortage fund.
DisposeHandle( shortageFund );
shortageFund = temp;
userHasSeenAlert = FALSE;
}
}
}
/*------------------------------*** MEMORYSHORTAGE ***--------------------------------*/
/*
called from the growzone proc when memory needs to be freed. This releases some or all
of the shortage fund, but you can override it to make additional sacrifices if need be.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::MemoryShortage( const Size bytesShort )
{
// this is called when the memory manager gets into dire straits. We can free some or all
// of our emergency fund to satisfy the request. If we succeed, we return TRUE, else FALSE.
Size fundSize = 0;
Handle gzHandle;
// get the handle the mem manager is dealing with at the moment. This is important since
// this might be the shortage fund itself. If it is, we can't resize it, so we really
// are in deep do-do. In this case, we flag memIsShort and hope the user will not ignore
// the message!
gzHandle = GZSaveHnd();
if ( gzHandle != shortageFund )
{
fundSize = GetHandleSize( shortageFund );
// release all or some of the memory to try and satisfy the request
if ( fundSize <= bytesShort )
SetHandleSize( shortageFund, 0 );
else
SetHandleSize( shortageFund, fundSize - bytesShort );
}
// flag the shortage so we can inform the user
memIsShort = TRUE;
// did we actually manage to free the requested amount?
return( fundSize > bytesShort );
}
/*------------------------------*** PROCESS1EVENT ***---------------------------------*/
/*
get an event, dispatch an event, get an event, dispatch...
----------------------------------------------------------------------------------------*/
void ZApplication::Process1Event()
{
EventRecord theEvent;
zEH->GetAnEvent( &theEvent );
zEH->DispatchAnEvent( &theEvent );
// timer queue is iterated at the end of handling an event
TimerTimer();
}
/*------------------------------*** PROCESS1EVENT ***---------------------------------*/
/*
dispatch an event that was fetched externally, for example by a library such as Nav
Services.
----------------------------------------------------------------------------------------*/
void ZApplication::Process1Event( EventRecord* anExternalEvent )
{
zEH->DispatchAnEvent( anExternalEvent );
TimerTimer();
}
/*----------------------------*** PROCESSALLEVENTS ***--------------------------------*/
/*
process all pending events, until a null event arrives
----------------------------------------------------------------------------------------*/
void ZApplication::ProcessAllEvents()
{
EventRecord theEvent;
do
{
zEH->GetAnEvent( &theEvent );
zEH->DispatchAnEvent( &theEvent );
// timer queue is iterated at the end of handling an event
TimerTimer();
}
while( theEvent.what != nullEvent );
}
/*-----------------------------*** GETCURRENTEVENT ***--------------------------------*/
/*
return the current event in progress, with FALSE if it was a null event, else TRUE.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::GetCurrentEvent( EventRecord* anEvent )
{
zEH->GetLatestEvent( anEvent );
return( anEvent->what != nullEvent );
}
/*--------------------------------*** GETCLICKS ***-----------------------------------*/
/*
return the number of clicks counted by the event handler in the same place. Returns 1 for
single click, 2 for double, 3 for triple, etc.
----------------------------------------------------------------------------------------*/
short ZApplication::GetClicks()
{
return zEH->GetClicks();
}
/*-------------------------------*** INBACKGROUND ***---------------------------------*/
/*
return TRUE if the application is in the background, FALSE in foreground.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::InBackground()
{
return zEH->InBackground();
}
/*-----------------------------------*** QUIT ***-------------------------------------*/
/*
close all of the windows. If successful, delete the application and return TRUE
----------------------------------------------------------------------------------------*/
Boolean ZApplication::Quit()
{
Boolean wereDone = done;
if ( phase == kQuitting )
{
CloseAll();
// copy <done> locally to avoid reading bad ref after class deleted
wereDone = done;
// the Quit can be abandoned by resetting <done> to FALSE. If this
// has not occurred, then truly say goodbye.
if ( done )
{
#if _USE_NAVIGATION_SERVICES
if ( gMacInfo.hasNavigationServices )
NavUnload();
#endif
#if APPEARANCE_MGR_AWARE
if ( gMacInfo.hasAppearanceMgr )
(void) UnregisterAppearanceClient();
#endif
ShutDown();
ForgetThis();
}
}
return wereDone;
}
/*-------------------------------*** REQUESTQUIT ***----------------------------------*/
/*
ask the application to quit. This is called by choosing Quit from the File menu, for example.
----------------------------------------------------------------------------------------*/
void ZApplication::RequestQuit()
{
done = TRUE;
}
/*----------------------------*** HANDLEAPPLEEVENT ***--------------------------------*/
/*
Handle the four required apple events. If you override this to handle your own apple
events, be sure to call the inherited method for everything else.
----------------------------------------------------------------------------------------*/
void ZApplication::HandleAppleEvent( AEEventClass aeClass, AEEventID aeID,
AppleEvent* aeEvt, AppleEvent* reply )
{
if ( aeClass == kCoreEventClass )
{
FSSpec aFile;
AEDescList docList;
long i, n;
AEKeyword keyWrd;
DescType retType;
Size actualSize;
FInfo fi;
switch ( aeID )
{
case kAEReopenApplication:
if ( GetFrontWindow())
break;
// fall through to normal open app event case if there are no windows:
case kAEOpenApplication:
#if MAKE_UNTITLED_STARTUP_WINDOW
OpenNewWindowType( kDefaultWindowType );
#endif
break;
case kAEOpenDocuments:
// do not handle this event if a modal dialog is up
if ( gWindowManager->IsDialog( GetFrontWindow()))
FailOSErr( kModalDialogOnScreenErr );
FailOSErr( AEGetParamDesc( aeEvt, keyDirectObject, typeAEList, &docList ));
FailOSErr( AECountItems( &docList, &n ));
// get each document and open it
try
{
for (i = 1; i <= n; i++)
{
FailOSErr( AEGetNthPtr( &docList,
i,
typeFSS,
&keyWrd,
&retType,
&aFile,
sizeof( FSSpec ),
&actualSize ));
FailOSErr( FSpGetFInfo( &aFile, &fi ));
// open the file into a window and select it, provided
// it really is one we can open. If the file is a prefs file, call up
// the kDoPreferences command:
if ( fi.fdType == 'pref' )
HandleCommand( kCmdDoPreferences );
else
{
if ( CanOpenFileType( fi.fdType ))
OpenFile( aFile, fi.fdType, ( fi.fdFlags & kIsStationery ) == kIsStationery );
}
}
}
catch( OSErr err )
{
AEDisposeDesc( &docList );
throw err;
}
FailOSErr( AEDisposeDesc( &docList ));
break;
case kAEPrintDocuments:
if ( gWindowManager->IsDialog( GetFrontWindow()))
FailOSErr( kModalDialogOnScreenErr );
FailOSErr( AEGetParamDesc( aeEvt, keyDirectObject, typeAEList, &docList ));
FailOSErr( AECountItems( &docList, &n ));
// ask the application to show the page setup dialog
if ( n > 0 )
DoPageSetup();
// get each document passed by the finder
try
{
for (i = 1; i <= n; i++)
{
FailOSErr( AEGetNthPtr( &docList,
i,
typeFSS,
&keyWrd,
&retType,
&aFile,
sizeof( FSSpec ),
&actualSize ));
FailOSErr( FSpGetFInfo( &aFile, &fi ));
// open the file into a window and select it
if ( CanOpenFileType( fi.fdType ))
{
ZWindow* zw;
zw = OpenFile( aFile, fi.fdType, ( fi.fdFlags & kIsStationery ) == kIsStationery );
// update the window (not strictly needed, but looks better)
zw->PerformUpdate();
// ask the application to print the document
DoPrint();
// that done, we can close the window and move on to the next
zw->Close( GetPhase());
}
}
}
catch( OSErr err )
{
AEDisposeDesc( &docList );
throw err;
}
FailOSErr( AEDisposeDesc( &docList ));
break;
case kAEQuitApplication:
RequestQuit();
break;
default:
ZCommander::HandleAppleEvent( aeClass, aeID, aeEvt, reply );
break;
}
}
else
ZCommander::HandleAppleEvent( aeClass, aeID, aeEvt, reply );
}
/*-------------------------------*** HANDLEERROR ***----------------------------------*/
/*
display an alert indicating the error (some errors do nothing, likewise noErr is ignored)
----------------------------------------------------------------------------------------*/
void ZApplication::HandleError( OSErr theErr )
{
// handles the error passed by displaying an alert. This is called by the exception
// handler for the application, but you can call it at any time. Some errors are "silent"
// in that the exception does not result in a message. These include userCanceled, kSilent
// Err, and aeEventNotHandled. Override this if you want to handle errors differently.
gMenuBar->SetTitleHilite( 0, FALSE );
if ( theErr != userCanceledErr &&
theErr != kSilentErr &&
theErr != noErr &&
theErr != errAEEventNotHandled )
{
StringHandle errExpH;
Str255 errMsgStr;
Str31 errExplStr;
Str15 errIDStr;
gMenuBar->ShowHideMenuBar( MBAR_SHOW );
NumToString( theErr, errIDStr );
// try to build a meaningful error message by looking for an 'Estr'
// resource with the same iD as the error. If found, this is concatenated
// onto the generic error stub and displayed. If not found, the default
// explanation "an error occurred" is used.
GetIndString( errMsgStr, 128, 10 );
errExpH = (StringHandle) GetResource( 'Estr', theErr );
if ( errExpH )
{
ConcatPStrings( errMsgStr, *errExpH );
ReleaseResource((Handle) errExpH );
}
else
{
GetIndString( errExplStr, 128, 11 );
ConcatPStrings( errMsgStr, errExplStr );
}
ParamText( errIDStr, errMsgStr, NULL, NULL );
(void) NotifyAlert( kExceptionAlertID, ntMinimalAlert );
}
}
/*------------------------*** WAITAPPLICATIONFOREGROUND ***---------------------------*/
/*
handle events until the application is not in the background. Used by NotifyAlert().
----------------------------------------------------------------------------------------*/
void ZApplication::WaitApplicationForeground()
{
while( InBackground())
Process1Event();
}
#pragma mark -
/*------------------------------*** HANDLECOMMAND ***---------------------------------*/
/*
Handle commands (menu items) that apply to the application as a whole, like Quit.
----------------------------------------------------------------------------------------*/
void ZApplication::HandleCommand( const long aCmd )
{
// handle commands at the application level. This includes quit, new, open, etc.
FSSpec aFile;
OSType anFType;
FInfo fi;
switch( aCmd )
{
case kCmdAbout:
AboutBox();
break;
case kCmdNew:
OpenNewWindowType( kDefaultWindowType ); // open a new "untitled" window
break;
case kCmdOpen:
if ( PickFile( &aFile, &anFType )) // choose a file
{
// is it a stationery (template) file?
FSpGetFInfo( &aFile, &fi );
OpenFile( aFile, anFType, ( fi.fdFlags & kIsStationery ) == kIsStationery ); // open the file into a window
}
break;
case kCmdQuit:
RequestQuit(); // the app should now quit
break;
case kCmdPageSetup:
DoPageSetup();
break;
case kCmdPrint:
DoPrint();
break;
case kCmdUndo:
if ( curUndoTask ) // undo item available?
{
SetWatchCursor();
if (curUndoTask->IsUndone()) // undo or redo?
curUndoTask->Redo(); // redo the task
else
curUndoTask->Undo(); // undo the task
}
break;
case kCmdDoPreferences:
DoPreferences();
break;
}
}
/*------------------------------*** HANDLECOMMAND ***---------------------------------*/
void ZApplication::HandleCommand( const short menuID, const short itemID )
{
GrafPtr savePort;
Str255 daName;
if ( menuID == kAppleMenuID &&
itemID > 2 )
{
// open desk accessories.
GetMenuItemText( GetMenuHandle( menuID ), itemID, daName );
GetPort( &savePort );
OpenDeskAcc( daName );
SetPort( savePort );
}
}
/*-------------------------------*** UPDATEMENUS ***----------------------------------*/
/*
enable the menu commands that the app can handle at the moment.
----------------------------------------------------------------------------------------*/
void ZApplication::UpdateMenus()
{
// enable the menu commands that pertain to the application. This includes "New",
// "Quit" and "About" amongst others
// apple menu
gMenuBar->EnableCommand( kCmdAbout );
// file menu
// if memory is currently short, do not enable new or open, since those are the
// commands that are likely to allocate a lot more memory, which we do not have.
if (! memIsShort)
{
gMenuBar->EnableCommand( kCmdNew );
gMenuBar->EnableCommand( kCmdOpen );
}
gMenuBar->EnableCommand( kCmdQuit );
// enable the printing items if there is a printer and the front window
// supports printing.
if ( itsPrinter )
{
gMenuBar->EnableCommand( kCmdPageSetup );
ZWindow* fWindow = GetFrontWindow();
if (fWindow && fWindow->IsPrintable())
gMenuBar->EnableCommand( kCmdPrint );
}
// edit:
UpdateUndo();
gMenuBar->EnableCommand( kCmdDoPreferences );
}
/*-------------------------------*** DOPAGESETUP ***----------------------------------*/
/*
handle the Page Setup command
----------------------------------------------------------------------------------------*/
void ZApplication::DoPageSetup()
{
if ( itsPrinter )
{
gWindowManager->Deactivate();
itsPrinter->PageSetUp();
gWindowManager->Activate();
}
}
/*----------------------------------*** DOPRINT ***-----------------------------------*/
/*
handle the Print command
----------------------------------------------------------------------------------------*/
void ZApplication::DoPrint()
{
if ( itsPrinter )
{
ZWindow* aWindow = GetFrontWindow();
gWindowManager->Deactivate();
itsPrinter->Print( aWindow );
gWindowManager->Activate();
}
}
/*---------------------------------*** ABOUTBOX ***-----------------------------------*/
/*
display the application's about box
----------------------------------------------------------------------------------------*/
void ZApplication::AboutBox()
{
gWindowManager->DeactivateForDialog( kAboutBoxID, TRUE );
(void) Alert( kAboutBoxID, NULL );
gWindowManager->Activate();
}
/*----------------------------------*** SETTASK ***-----------------------------------*/
/*
update the current global undo task
----------------------------------------------------------------------------------------*/
void ZApplication::SetTask( ZUndoTask* aTask )
{
if ( curUndoTask )
ForgetObject( curUndoTask );
curUndoTask = aTask;
}
/*--------------------------------*** UPDATEUNDO ***----------------------------------*/
/*
update the Undo menu item to reflect the current undo task
----------------------------------------------------------------------------------------*/
void ZApplication::UpdateUndo()
{
Str255 undoMenuStr;
if ( curUndoTask )
{
// a task available, so update the undo menu item to the task string.
Str63 taskStr;
if (curUndoTask->IsUndone())
GetIndString( undoMenuStr, kMiscStrListID, kRedoStrIndex );
else
GetIndString( undoMenuStr, kMiscStrListID, kUndoStrIndex );
// add the task name
curUndoTask->GetTaskString( taskStr );
ConcatPStrings( undoMenuStr, taskStr );
// set the menu item to the task name
gMenuBar->SetCommandText( kCmdUndo, undoMenuStr );
// there is a task, but does it refer to the front window? If not,
// then do not actually enable the command.
if ((curUndoTask->GetUndoTarget() == GetFrontWindow()) &&
(curUndoTask->GetUndoTarget() != NULL))
gMenuBar->EnableCommand( kCmdUndo );
}
else
{
// if no task, just show a dimmed "Can't Undo"
gMenuBar->SetCommandText( kCmdUndo, kMiscStrListID, kCantUndoStrIndex );
}
}
/*---------------------------------*** DOSUSPEND ***----------------------------------*/
/*
send a suspend message
----------------------------------------------------------------------------------------*/
void ZApplication::DoSuspend()
{
SendMessage( msgApplicationSuspending, 0 );
gWindowManager->Suspend();
}
/*---------------------------------*** DORESUME ***-----------------------------------*/
/*
send a resume message
----------------------------------------------------------------------------------------*/
void ZApplication::DoResume()
{
gWindowManager->Resume();
SendMessage( msgApplicationResuming, 0 );
}
#pragma mark -
/*-------------------------------*** INITMENUBAR ***----------------------------------*/
/*
set up the menubar object for managing the main menus
----------------------------------------------------------------------------------------*/
void ZApplication::InitMenuBar()
{
// installs the menu bar. By default, we just install 'MBAR' ID = 128, which means you
// don't need to override this to get other menus- just create the resources you want.
FailNIL( gMenuBar = new ZMenuBar( kStdMenubarID ));
gMenuBar->InitMenuBar();
}
/*---------------------------*** MOUSENOTINANYWINDOW ***------------------------------*/
/*
the mouse is not over any window. By default this just sets the default cursor shape, but
you can override this to get informed if you are interested in this fact.
----------------------------------------------------------------------------------------*/
void ZApplication::MouseNotInAnyWindow( const Point globalMouse )
{
ResumeCursorAnimation();
SetCursorShape( 0 );
}
/*----------------------------*** HANDLEMBARHIDING ***--------------------------------*/
/*
deal with the menubar hiding for auto hiding feature
----------------------------------------------------------------------------------------*/
void ZApplication::HandleMBarHiding( const Point globalPt )
{
#if _AUTO_MBAR_HIDING
if ( ! InBackground())
gMenuBar->ShowHideMenuBar( MBAR_HIDE_MOUSEAWARE, globalPt );
#endif
}
/*--------------------------------*** MAKECLIPBOARD ***-------------------------------*/
/*
Make the clipboard object. By default, this is a ZClipboard.
----------------------------------------------------------------------------------------*/
void ZApplication::MakeClipboard()
{
FailNIL( gClipboard = new ZClipboard());
}
/*---------------------------------*** MAKEHELPERS ***--------------------------------*/
/*
Make the helper objects. The standard helpers are the event handler, the window manager
and the applescript handlers
----------------------------------------------------------------------------------------*/
void ZApplication::MakeHelpers()
{
// make the event handler
FailNIL( zEH = new ZEventHandler());
// make the window manager for handling floating windows, etc.
FailNIL( gWindowManager = new ZWindowManager());
// install handlers for the four required events
zEH->InstallApplescriptHandlers();
}
/*--------------------------------*** MAKEPRINTER ***---------------------------------*/
/*
creates a new printer object for handling the print commands
----------------------------------------------------------------------------------------*/
void ZApplication::MakePrinter()
{
#if PRINTING_ON
try
{
FailNIL( itsPrinter = new ZPrinter());
}
catch( OSErr err )
{
itsPrinter = NULL;
// note, to not have a printer is not fatal- in this case we carry on,
// but print commands will be greyed out. This may happen if no printer
// driver is selected in the chooser.
}
#endif
}
#pragma mark -
/*----------------------------*** MAKENEWWINDOWTYPE ***-------------------------------*/
/*
creates a new window object and initialises it. It does not show it yet. Normally you will
override this method to create your own useful kinds of windows. However, even if you don't
you'll still get a plain window that works.
----------------------------------------------------------------------------------------*/
ZWindow* ZApplication::MakeNewWindowType( OSType aType )
{
ZWindow* aWindow;
#ifdef USER_DEFAULT_WINDOW_TYPE
FailNIL( aWindow = new USER_DEFAULT_WINDOW_TYPE( this, kUntitledWindowID ));
#else
FailNIL( aWindow = new ZWindow( this, kUntitledWindowID ));
#endif
try
{
aWindow->InitZWindow();
}
catch( OSErr err )
{
ForgetObject( aWindow );
throw err;
}
return aWindow;
}
/*----------------------------*** OPENNEWWINDOWTYPE ***-------------------------------*/
/*
create a new window object of the requisite type.
----------------------------------------------------------------------------------------*/
ZWindow* ZApplication::OpenNewWindowType( OSType aType )
{
ZWindow* theWindow;
gMenuBar->SetZoomSourceToCommand( kCmdNew );
theWindow = MakeNewWindowType( aType );
if ( theWindow )
{
theWindow->Place();
theWindow->Select();
}
return theWindow;
}
/*---------------------------------*** CLOSEALL ***-----------------------------------*/
/*
closes all of the application's windows. If one refuses to close, this will abort a Quit.
This iterates backwards through its underlings list looking for windows, rather than
relying on the window lists, since windows will also close their own underling windows so
the whole commander tree is cleaned up properly. If your app organises things differently,
you may need to override this.
----------------------------------------------------------------------------------------*/
void ZApplication::CloseAll( Boolean closeFloaters )
{
long i;
ZWindow* aWindow;
if ( itsUnderlings )
{
i = itsUnderlings->CountItems();
while( i )
{
aWindow = dynamic_cast<ZWindow*>( itsUnderlings->GetObject( i-- ));
if ( aWindow )
{
if ( phase == kQuitting )
{
if ( ! aWindow->Close( phase ))
{
phase = kRunning;
done = FALSE;
break;
}
}
else
{
if (! aWindow->NoAutoClose() && ( aWindow->Floats() == closeFloaters ))
{
if ( ! aWindow->Close( phase ))
{
phase = kRunning;
done = FALSE;
break;
}
}
}
}
}
}
}
/*------------------------------*** GETFRONTWINDOW ***--------------------------------*/
/*
returns the active window object, if there is one
----------------------------------------------------------------------------------------*/
ZWindow* ZApplication::GetFrontWindow()
{
return ( gWindowManager->GetTopWindow());
}
/*----------------------------------*** PICKFILE ***----------------------------------*/
/*
displays the standard file dialog for selecting a file, and returns its filespec.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::PickFile( FSSpec* aFile, OSType* fType )
{
// uses standard file to choose a file to open to a window. By default, no files types
// are added to the list, which we here interpret to mean "show all files".
#if _USE_NAVIGATION_SERVICES
// using navigation services, so we need to tackle things a little differently. The result
// from this method is the same- namely a single filespec and a type.
if ( gMacInfo.hasNavigationServices )
{
OSErr theErr;
NavReplyRecord navReply;
NavDialogOptions navOptions;
Boolean result = FALSE;
FailOSErr( NavGetDefaultDialogOptions( &navOptions ));
GetName( navOptions.clientName );
// if file list empty, allow "all files"
if ((*itsFileTypes)->osTypeCount == 0 )
navOptions.dialogOptionFlags |= kNavAllFilesInPopup;
// only permit one selection here:
navOptions.dialogOptionFlags &= ~kNavAllowMultipleFiles;
gWindowManager->DeactivateForDialog( -1, FALSE );
StopCursorAnimation();
theErr = NavGetFile( NULL,
&navReply,
&navOptions,
gNavEventHandler,
NULL,
NULL,
(NavTypeListHandle) itsFileTypes,
(NavCallBackUserData) this );
gWindowManager->Activate();
// extract info. Since we only allowed 1 selection, this will be the first item:
if (( theErr == noErr ) && navReply.validRecord )
{
AEDesc specDesc;
FInfo fi;
FailOSErr( AEGetNthDesc( &navReply.selection, 1, typeFSS, NULL, &specDesc ));
BlockMoveData( *specDesc.dataHandle, aFile, sizeof( FSSpec ));
// to get the type we need to do a FsGetFInfo:
FSpGetFInfo( aFile, &fi );
*fType = fi.fdType;
result = TRUE;
}
else
result = FALSE;
FailOSErr( NavDisposeReply( &navReply ));
return result;
}
else
{
#endif
StandardFileReply aReply;
OSType* listPtr;
short numTypes;
HLock((Handle) itsFileTypes );
numTypes = (*itsFileTypes )->osTypeCount;
listPtr = &(*itsFileTypes)->osType[0];
// if no types in the list, show all of them
if( numTypes <= 0 )
numTypes = -1;
StopCursorAnimation();
gWindowManager->DeactivateForDialog( sfGetDialogID );
// display the dialog
StandardGetFile( NULL, numTypes, listPtr, &aReply );
HUnlock((Handle) itsFileTypes );
gWindowManager->Activate();
if ( aReply.sfGood )
{
*aFile = aReply.sfFile;
*fType = aReply.sfType;
return TRUE;
}
else
return FALSE;
#if _USE_NAVIGATION_SERVICES
}
#endif
}
/*----------------------------------*** OPENFILE ***----------------------------------*/
/*
creates a new window and asks it to open the file. It then shows the window and activates it.
If your application doesn't have a classic "document" interface, but e.g. simply processes
files dropped on the application, you can override this method to process the files. It will
be called once for each file dropped on the application that we know how to open.
----------------------------------------------------------------------------------------*/
ZWindow* ZApplication::OpenFile( const FSSpec& aFile, const OSType fType, Boolean isStationery )
{
// opens the file into a new window. This is equivalent to OpenNewWindow, but
// for when the user chose a file with the Open command
ZWindow* aWindow = NULL;
SetWatchCursor();
aWindow = MakeNewWindowType( fType );
// window created, so ask it to open the chosen file
if ( aWindow )
{
try
{
aWindow->SetFile( aFile );
aWindow->OpenFile( fType, isStationery );
aWindow->Place();
aWindow->Select();
}
catch( OSErr err )
{
ForgetObject( aWindow );
throw err;
}
}
return aWindow;
}
/*--------------------------------*** ADDFILETYPE ***---------------------------------*/
/*
adds <aType> to the list of types this application will show in the Open dialog. You can
call this for each type your application can open. This ignores duplicates.
-----------------------------------------------------------------------------------------*/
void ZApplication::AddFileType( const OSType aType )
{
// adds the file type to the list of types, if not already there
short nTypes, i;
nTypes = (*itsFileTypes)->osTypeCount;
// check that the file type we are adding is unique in the list
for ( i = 0; i < nTypes; i++ )
{
if ((*itsFileTypes)->osType[i] == aType )
return;
}
// if we are still here, type is unique, so append it
// first grow the handle
SetHandleSize((Handle) itsFileTypes, GetHandleSize((Handle) itsFileTypes ) + sizeof( OSType ));
FailMemError();
// set the new entry
(*itsFileTypes)->osType[ nTypes ] = aType;
(*itsFileTypes)->osTypeCount++;
}
/*------------------------------*** CANOPENFILETYPE ***--------------------------------*/
/*
return TRUE if this application can open files of the given type. The default method
determines if the type is in the fileType list or not. If the list is empty, this always
return TRUE.
-----------------------------------------------------------------------------------------*/
Boolean ZApplication::CanOpenFileType( const OSType aType )
{
short nTypes, i;
nTypes = (*itsFileTypes)->osTypeCount;
if ( nTypes > 0 )
{
for ( i = 0; i < nTypes; i++ )
{
if ( (*itsFileTypes)->osType[i] == aType )
return TRUE;
}
return FALSE;
}
else
return TRUE;
}
/*------------------------------*** REGISTERCLASSES ***-------------------------------*/
/*
in order to use persistent objects, classes must be registered. You only need to register
classes that you will want to create from a stream. By default this registers those
required classes that could be constructed from a stream- override this to extend the
range of classes, but always call the inherited method.
----------------------------------------------------------------------------------------*/
void ZApplication::RegisterClasses()
{
#if _MACZOOP_STREAMS
FailNIL( gClasses );
REGISTERCLASS( ZComrade );
REGISTERCLASS( ZCommander );
REGISTERCLASS( ZArray );
REGISTERCLASS( ZWindow );
REGISTERCLASS( ZApplication );
REGISTERCLASS( ZObjectList );
#endif
}
#pragma mark -
/*---------------------------------*** ZGROWFUNC ***----------------------------------*/
/*
memory manager callback proc.
----------------------------------------------------------------------------------------*/
static pascal long ZGrowFunc( Size bytesShort )
{
Boolean bytesFreed;
// call application object to free some memory
try
{
bytesFreed = gApplication->MemoryShortage( bytesShort );
}
catch(...)
{
bytesFreed = 0;
// do NOT proagate any exception, since we are within a toolbox callback
}
return bytesFreed;
}
/*------------------------------*** RUNAPPLICATION ***--------------------------------*/
/*
standard function to run the application. Your main() function must make the relevant
ZApplication object, assign it to gApplication, then call this. Note that this will happen
automagically as long as you define APP_CLASS_NAME to be the classname of your application
object. This definition is set up in <ProjectSettings.h>
----------------------------------------------------------------------------------------*/
void RunApplication()
{
if (gApplication)
{
try
{
gApplication->InitMacZoop(); // initialise the whole kaboodle. This is
// NOT done by the constructor since you might
// want to override the initialisation.
}
catch( OSErr err )
{
// if an exception occurs during startup, the application cannot run, since
// everything must be properly built and in place before handling events. In this
// case we display a fatal alert message and exit.
Str31 appName, errStr;
gApplication->GetName( appName );
NumToString( err, errStr );
ParamText( appName, errStr, NULL, NULL );
StopCursorAnimation();
(void) Alert( kFatalStartupErrAlertID, NULL );
ExitToShell();
}
// initialisation is now complete, so we can go ahead and run the thing
do
{
gApplication->Run(); // run the application until the user quits
}
while (! gApplication->Quit()); // try to quit
}
}
#if _USE_NAVIGATION_SERVICES
pascal void ZNavEventCallback( NavEventCallbackMessage cbMessage,
NavCBRecPtr cbParams,
NavCallBackUserData cbData )
{
GrafPtr savePort;
switch ( cbMessage )
{
case kNavCBEvent:
GetPort( &savePort );
#if UNIVERSAL_INTERFACES_VERSION < 0x0320
if ( cbParams->eventData.event->what == updateEvt ||
cbParams->eventData.event->what == activateEvt ||
cbParams->eventData.event->what == nullEvent )
gApplication->Process1Event( cbParams->eventData.event );
#else
if ( cbParams->eventData.eventDataParms.event->what == updateEvt ||
cbParams->eventData.eventDataParms.event->what == activateEvt ||
cbParams->eventData.eventDataParms.event->what == nullEvent )
gApplication->Process1Event( cbParams->eventData.eventDataParms.event );
#endif
SetPort( savePort );
break;
default:
break;
}
}
#endif